cargo-rustc \
cargo-verify-project \
cargo-git-checkout \
+ cargo-test \
SRC = $(shell find src -name '*.rs' -not -path 'src/bin*')
let update = options.update_remotes;
- ops::compile(&root, update, shell).map(|_| None).map_err(|err| {
+ ops::compile(&root, update, "compile", shell).map(|_| None).map_err(|err| {
CliError::from_boxed(err, 101)
})
}
--- /dev/null
+#![crate_id="cargo-test"]
+#![feature(phase)]
+
+#[phase(plugin, link)]
+extern crate cargo;
+extern crate serialize;
+
+#[phase(plugin, link)]
+extern crate hammer;
+
+use std::os;
+use std::io::fs;
+
+use cargo::ops;
+use cargo::{execute_main_without_stdin};
+use cargo::core::{MultiShell};
+use cargo::util;
+use cargo::util::{CliResult, CliError};
+use cargo::util::important_paths::find_project;
+
+#[deriving(PartialEq,Clone,Decodable)]
+struct Options {
+ manifest_path: Option<String>,
+ rest: Vec<String>
+}
+
+hammer_config!(Options "Run the package's test suite")
+
+fn main() {
+ execute_main_without_stdin(execute);
+}
+
+fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
+ let root = match options.manifest_path {
+ Some(path) => Path::new(path),
+ None => try!(find_project(os::getcwd(), "Cargo.toml")
+ .map(|path| path.join("Cargo.toml"))
+ .map_err(|_| {
+ CliError::new("Could not find Cargo.toml in this \
+ directory or any parent directory",
+ 102)
+ }))
+ };
+
+ try!(ops::compile(&root, false, "test", shell).map(|_| None::<()>).map_err(|err| {
+ CliError::from_boxed(err, 101)
+ }));
+
+ let test_dir = root.dir_path().join("target").join("tests");
+
+ let mut walk = try!(fs::walk_dir(&test_dir).map_err(|e| {
+ CliError::from_error(e, 1)
+ }));
+
+ for file in walk {
+ try!(util::process(file).exec().map_err(|e| CliError::from_boxed(e.box_error(), 1)));
+ }
+
+ Ok(None)
+}
-use semver::Version;
use core::{VersionReq,SourceId};
use util::CargoResult;
}
impl Dependency {
- pub fn new(name: &str, req: &VersionReq,
- namespace: &SourceId) -> Dependency {
- Dependency {
- name: name.to_str(),
- namespace: namespace.clone(),
- req: req.clone()
- }
- }
-
pub fn parse(name: &str, version: Option<&str>,
- namespace: &SourceId) -> CargoResult<Dependency> {
-
+ namespace: &SourceId) -> CargoResult<Dependency>
+ {
let version = match version {
Some(v) => try!(VersionReq::parse(v)),
None => VersionReq::any()
})
}
- pub fn exact(name: &str, version: &Version,
- namespace: &SourceId) -> Dependency {
- Dependency {
- name: name.to_str(),
- namespace: namespace.clone(),
- req: VersionReq::exact(version)
- }
- }
-
pub fn get_version_req<'a>(&'a self) -> &'a VersionReq {
&self.req
}
}
}
-#[deriving(Show,Clone,PartialEq,Encodable)]
+#[deriving(Show, Clone, PartialEq, Hash, Encodable)]
pub enum LibKind {
Lib,
Rlib,
}
}
-#[deriving(Show,Clone,PartialEq,Encodable)]
+#[deriving(Show, Clone, Hash, PartialEq, Encodable)]
pub enum TargetKind {
LibTarget(Vec<LibKind>),
BinTarget
}
-#[deriving(Clone,PartialEq)]
+#[deriving(Clone, Hash, PartialEq)]
+pub struct Profile {
+ env: String, // compile, test, dev, bench, etc.
+ opt_level: uint,
+ debug: bool,
+ test: bool
+}
+
+impl Profile {
+ pub fn default(env: &str) -> Profile {
+ Profile {
+ env: env.to_str(), // run in the default environment only
+ opt_level: 0,
+ debug: true,
+ test: false // whether or not to pass --test
+ }
+ }
+
+ pub fn is_compile(&self) -> bool {
+ self.env.as_slice() == "compile"
+ }
+
+ pub fn is_test(&self) -> bool {
+ self.test
+ }
+
+ pub fn get_env<'a>(&'a self) -> &'a str {
+ self.env.as_slice()
+ }
+
+ pub fn opt_level(mut self, level: uint) -> Profile {
+ self.opt_level = level;
+ self
+ }
+
+ pub fn debug(mut self, debug: bool) -> Profile {
+ self.debug = debug;
+ self
+ }
+
+ pub fn test(mut self, test: bool) -> Profile {
+ self.test = test;
+ self
+ }
+}
+
+#[deriving(Clone, Hash, PartialEq)]
pub struct Target {
kind: TargetKind,
name: String,
- path: Path
+ path: Path,
+ profile: Profile
}
#[deriving(Encodable)]
impl Target {
pub fn lib_target(name: &str, crate_targets: Vec<LibKind>,
- path: &Path) -> Target {
+ path: &Path, profile: &Profile) -> Target {
Target {
kind: LibTarget(crate_targets),
name: name.to_str(),
- path: path.clone()
+ path: path.clone(),
+ profile: profile.clone()
}
}
- pub fn bin_target(name: &str, path: &Path) -> Target {
+ pub fn bin_target(name: &str, path: &Path, profile: &Profile) -> Target {
Target {
kind: BinTarget,
name: name.to_str(),
- path: path.clone()
+ path: path.clone(),
+ profile: profile.clone()
}
}
}
}
+ pub fn get_profile<'a>(&'a self) -> &'a Profile {
+ &self.profile
+ }
+
pub fn rustc_crate_types(&self) -> Vec<&'static str> {
match self.kind {
LibTarget(ref kinds) => {
Manifest,
Target,
TargetKind,
+ Profile
};
pub use self::package::{
ShellConfig
};
-pub use self::dependency::Dependency;
+pub use self::dependency::{
+ Dependency
+};
+
pub use self::version_req::VersionReq;
pub mod errors;
)
)
+// Added so that the try! macro below can refer to cargo::util, while
+// other external importers of this macro can use it as well.
+//
+// "Hygiene strikes again" - @acrichton
+mod cargo {
+ pub use super::util;
+}
+
+#[macro_export]
macro_rules! try (
($expr:expr) => ({
- use util::CargoError;
+ use cargo::util::CargoError;
match $expr.map_err(|err| err.to_error()) {
Ok(val) => val,
Err(err) => return Err(err)
use std::os;
use util::config::{Config, ConfigValue};
-use core::{MultiShell, Source, SourceId, PackageSet, resolver};
+use core::{MultiShell, Source, SourceId, PackageSet, Target, resolver};
use core::registry::PackageRegistry;
use ops;
use sources::{PathSource};
use util::{CargoResult, Wrap, config, internal, human};
-pub fn compile(manifest_path: &Path, update: bool, shell: &mut MultiShell) -> CargoResult<()> {
+pub fn compile(manifest_path: &Path, update: bool,
+ env: &str, shell: &mut MultiShell) -> CargoResult<()>
+{
log!(4, "compile; manifest-path={}", manifest_path.display());
let mut source = PathSource::for_path(&manifest_path.dir_path());
debug!("packages={}", packages);
+ let targets = package.get_targets().iter().filter(|target| {
+ target.get_profile().get_env() == env
+ }).collect::<Vec<&Target>>();
+
let mut config = try!(Config::new(shell, update));
- try!(ops::compile_packages(&package, &PackageSet::new(packages.as_slice()), &mut config));
+ try!(ops::compile_targets(targets.as_slice(), &package,
+ &PackageSet::new(packages.as_slice()), &mut config));
Ok(())
}
use std::io;
use std::io::{File, IoError};
use std::str;
+use std::hash::sip::SipHasher;
+use std::hash::Hasher;
use core::{Package, PackageSet, Target};
use util;
config: &'b mut Config<'b>
}
-pub fn compile_packages<'a>(pkg: &Package, deps: &PackageSet,
- config: &'a mut Config<'a>) -> CargoResult<()> {
+pub fn compile_targets<'a>(targets: &[&Target], pkg: &Package, deps: &PackageSet,
+ config: &'a mut Config<'a>) -> CargoResult<()> {
- debug!("compile_packages; pkg={}; deps={}", pkg, deps);
+ debug!("compile_targets; targets={}; pkg={}; deps={}", targets, pkg, deps);
let target_dir = pkg.get_absolute_target_dir();
let deps_target_dir = target_dir.join("deps");
+ let tests_target_dir = target_dir.join("tests");
let output = try!(util::process("rustc").arg("-v").exec_with_output());
let rustc_version = str::from_utf8(output.output.as_slice()).unwrap();
internal(format!("Couldn't create the directory for dependencies for {} at {}",
pkg.get_name(), deps_target_dir.display()))));
+ try!(mk_target(&tests_target_dir).chain_error(||
+ internal(format!("Couldn't create the directory for tests for {} at {}",
+ pkg.get_name(), tests_target_dir.display()))));
+
let mut cx = Context {
dest: &deps_target_dir,
deps_dir: &deps_target_dir,
// Traverse the dependencies in topological order
for dep in try!(topsort(deps)).iter() {
- try!(compile_pkg(dep, &mut cx));
+ let targets = dep.get_targets().iter().filter(|target| {
+ // Only compile lib targets for dependencies
+ target.is_lib() && target.get_profile().is_compile()
+ }).collect::<Vec<&Target>>();
+
+ try!(compile(targets.as_slice(), dep, &mut cx));
}
cx.primary = true;
cx.dest = &target_dir;
- try!(compile_pkg(pkg, &mut cx));
+
+ try!(compile(targets, pkg, &mut cx));
Ok(())
}
-fn compile_pkg(pkg: &Package, cx: &mut Context) -> CargoResult<()> {
+fn compile(targets: &[&Target], pkg: &Package, cx: &mut Context) -> CargoResult<()> {
debug!("compile_pkg; pkg={}; targets={}", pkg, pkg.get_targets());
+ if targets.is_empty() {
+ return Ok(());
+ }
+
// First check to see if this package is fresh.
//
// Note that we're compiling things in topological order, so if nothing has
//
// This is not quite accurate, we should only trigger forceful
// recompilations for downstream dependencies of ourselves, not everyone
- // compiled afterwards.
+ // compiled afterwards.a
+ //
+ // TODO: Figure out how this works with targets
let fingerprint_loc = cx.dest.join(format!(".{}.fingerprint",
pkg.get_name()));
- let (is_fresh, fingerprint) = try!(is_fresh(pkg, &fingerprint_loc, cx));
+ let (is_fresh, fingerprint) = try!(is_fresh(pkg, &fingerprint_loc, cx, targets));
if !cx.compiled_anything && is_fresh {
try!(cx.config.shell().status("Fresh", pkg));
return Ok(())
// command if one is present.
try!(cx.config.shell().status("Compiling", pkg));
+ // TODO: Should this be on the target or the package?
match pkg.get_manifest().get_build() {
Some(cmd) => try!(compile_custom(pkg, cmd, cx)),
None => {}
// After the custom command has run, execute rustc for all targets of our
// package.
- for target in pkg.get_targets().iter() {
- // Only compile lib targets for dependencies
- if cx.primary || target.is_lib() {
- try!(rustc(&pkg.get_root(), target, cx))
- }
+ for &target in targets.iter() {
+ try!(rustc(&pkg.get_root(), target, cx));
}
// Now that everything has successfully compiled, write our new fingerprint
}
fn is_fresh(dep: &Package, loc: &Path,
- cx: &mut Context) -> CargoResult<(bool, String)> {
- let new_fingerprint = format!("{}{}", cx.rustc_version,
+ cx: &mut Context, targets: &[&Target]) -> CargoResult<(bool, String)>
+{
+ let new_pkg_fingerprint = format!("{}{}", cx.rustc_version,
try!(dep.get_fingerprint(cx.config)));
+
+ let new_fingerprint = fingerprint(new_pkg_fingerprint, hash_targets(targets));
+
let mut file = match File::open(loc) {
Ok(file) => file,
Err(..) => return Ok((false, new_fingerprint)),
};
+
let old_fingerprint = try!(file.read_to_str());
log!(5, "old fingerprint: {}", old_fingerprint);
Ok((old_fingerprint == new_fingerprint, new_fingerprint))
}
+fn hash_targets(targets: &[&Target]) -> u64 {
+ let hasher = SipHasher::new_with_keys(0,0);
+ let targets = targets.iter().map(|t| (*t).clone()).collect::<Vec<Target>>();
+ hasher.hash(&targets)
+}
+
+fn fingerprint(package: String, profiles: u64) -> String {
+ let hasher = SipHasher::new_with_keys(0,0);
+ util::to_hex(hasher.hash(&(package, profiles)))
+}
+
fn mk_target(target: &Path) -> Result<(), IoError> {
io::fs::mkdir_recursive(target, io::UserRWX)
}
build_base_args(&mut args, target, crate_types, cx);
build_deps_args(&mut args, cx);
+
util::process("rustc")
.cwd(root.clone())
.args(args.as_slice())
into.push("--crate-type".to_str());
into.push(crate_type.to_str());
}
+
+ let mut out = cx.dest.clone();
+
+ if target.get_profile().is_test() {
+ into.push("--test".to_str());
+ out = out.join("tests");
+ }
+
into.push("--out-dir".to_str());
- into.push(cx.dest.display().to_str());
+ into.push(out.display().to_str());
}
fn build_deps_args(dst: &mut Args, cx: &Context) {
pub use self::cargo_compile::compile;
pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
-pub use self::cargo_rustc::compile_packages;
+pub use self::cargo_rustc::compile_targets;
mod cargo_compile;
mod cargo_read_manifest;
use std::fmt;
use std::hash::Hasher;
use std::hash::sip::SipHasher;
-use std::io::MemWriter;
use std::str;
-use serialize::hex::ToHex;
use core::source::{Source, SourceId, GitKind, Location, Remote, Local};
use core::{Package,PackageId,Summary};
-use util::{CargoResult,Config};
+use util::{CargoResult, Config, to_hex};
use sources::PathSource;
use sources::git::utils::{GitReference,GitRemote,Master,Other};
format!("{}-{}", ident, to_hex(hasher.hash(&location.to_str())))
}
-fn to_hex(num: u64) -> String {
- let mut writer = MemWriter::with_capacity(8);
- writer.write_le_u64(num).unwrap(); // this should never fail
- writer.get_ref().to_hex()
-}
-
impl<'a, 'b> Show for GitSource<'a, 'b> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
try!(write!(f, "git repo at {}", self.remote.get_location()));
--- /dev/null
+use std::io::MemWriter;
+
+use serialize::hex::ToHex;
+
+pub fn to_hex(num: u64) -> String {
+ let mut writer = MemWriter::with_capacity(8);
+ writer.write_le_u64(num).unwrap(); // this should never fail
+ writer.get_ref().to_hex()
+}
+
pub use self::errors::{CliError, FromError, ProcessError};
pub use self::errors::{process_error, internal_error, internal, human};
pub use self::paths::realpath;
+pub use self::hex::to_hex;
pub mod graph;
pub mod process_builder;
pub mod toml;
pub mod paths;
pub mod errors;
+pub mod hex;
pub fn extra_path(mut self, path: Path) -> ProcessBuilder {
// For now, just convert to a string, but we should do something better
- self.path.push(path.display().to_str());
+ self.path.unshift(path.display().to_str());
self
}
use url;
use core::{SourceId, GitKind};
-use core::manifest::{LibKind, Lib};
+use core::manifest::{LibKind, Lib, Profile};
use core::{Summary, Manifest, Target, Dependency, PackageId};
use core::source::{Location, Local, Remote};
use util::{CargoResult, Require, human};
DetailedDep(DetailedTomlDependency)
}
+
#[deriving(Encodable,Decodable,PartialEq,Clone,Show)]
pub struct DetailedTomlDependency {
version: Option<String>,
struct TomlTarget {
name: String,
crate_type: Option<Vec<String>>,
- path: Option<String>
+ path: Option<String>,
+ test: Option<bool>
}
fn normalize(lib: Option<&[TomlLibTarget]>,
bin: Option<&[TomlBinTarget]>) -> Vec<Target> {
log!(4, "normalizing toml targets; lib={}; bin={}", lib, bin);
+ fn target_profiles(target: &TomlTarget) -> Vec<Profile> {
+ let mut ret = vec!(Profile::default("compile"));
+
+ match target.test {
+ Some(true) | None => ret.push(Profile::default("test").test(true)),
+ _ => {}
+ };
+
+ ret
+ }
+
fn lib_targets(dst: &mut Vec<Target>, libs: &[TomlLibTarget]) {
let l = &libs[0];
let path = l.path.clone().unwrap_or_else(|| format!("src/{}.rs", l.name));
let crate_types = l.crate_type.clone().and_then(|kinds| {
LibKind::from_strs(kinds).ok()
}).unwrap_or_else(|| vec!(Lib));
- dst.push(Target::lib_target(l.name.as_slice(), crate_types,
- &Path::new(path)));
+
+ for profile in target_profiles(l).iter() {
+ dst.push(Target::lib_target(l.name.as_slice(), crate_types.clone(),
+ &Path::new(path.as_slice()), profile));
+ }
}
fn bin_targets(dst: &mut Vec<Target>, bins: &[TomlBinTarget],
default: |&TomlBinTarget| -> String) {
for bin in bins.iter() {
let path = bin.path.clone().unwrap_or_else(|| default(bin));
- dst.push(Target::bin_target(bin.name.as_slice(), &Path::new(path)));
+
+ for profile in target_profiles(bin).iter() {
+ dst.push(Target::bin_target(bin.name.as_slice(), &Path::new(path.as_slice()), profile));
+ }
}
}
pub fn escape_path(p: &Path) -> String {
p.display().to_str().as_slice().replace("\\", "\\\\")
}
+
+pub fn basic_bin_manifest(name: &str) -> String {
+ format!(r#"
+ [project]
+
+ name = "{}"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [[bin]]
+
+ name = "{}"
+ "#, name, name)
+}
+
+pub static COMPILING: &'static str = " Compiling";
+pub static FRESH: &'static str = " Fresh";
+pub static UPDATING: &'static str = " Updating";
use std::os;
use std::path;
-use support::{ResultTest, project, execs, main_file, escape_path};
+use support::{ResultTest, project, execs, main_file, escape_path, basic_bin_manifest};
+use support::COMPILING;
use hamcrest::{assert_that, existing_file};
use cargo;
use cargo::util::{process, realpath};
fn setup() {
}
-static COMPILING: &'static str = " Compiling";
-
-fn basic_bin_manifest(name: &str) -> String {
- format!(r#"
- [project]
-
- name = "{}"
- version = "0.5.0"
- authors = ["wycats@example.com"]
-
- [[bin]]
-
- name = "{}"
- "#, name, name)
-}
-
test!(cargo_compile_simple {
let p = project("foo")
.file("Cargo.toml", basic_bin_manifest("foo").as_slice())
use support::{ProjectBuilder, ResultTest, project, execs, main_file, paths};
use support::{escape_path, cargo_dir};
+use support::{COMPILING, FRESH, UPDATING};
use hamcrest::{assert_that,existing_file};
use cargo;
use cargo::util::{ProcessError, process};
-static COMPILING: &'static str = " Compiling";
-static FRESH: &'static str = " Fresh";
-static UPDATING: &'static str = " Updating";
-
fn setup() {
}
use std::io::timer;
use support::{ResultTest, project, execs, main_file, escape_path, cargo_dir};
+use support::{COMPILING, FRESH};
use hamcrest::{assert_that, existing_file};
use cargo;
use cargo::util::{process};
fn setup() {
}
-static COMPILING: &'static str = " Compiling";
-static FRESH: &'static str = " Fresh";
-
test!(cargo_compile_with_nested_deps_shorthand {
let p = project("foo")
.file("Cargo.toml", r#"
--- /dev/null
+use support::{project, execs, basic_bin_manifest, COMPILING};
+use hamcrest::{assert_that, existing_file};
+use cargo::util::process;
+
+fn setup() {}
+
+test!(cargo_test_simple {
+ let p = project("foo")
+ .file("Cargo.toml", basic_bin_manifest("foo").as_slice())
+ .file("src/foo.rs", r#"
+ fn hello() -> &'static str {
+ "hello"
+ }
+
+ pub fn main() {
+ println!("{}", hello())
+ }
+
+ #[test]
+ fn test_hello() {
+ assert_eq!(hello(), "hello")
+ }"#);
+
+ assert_that(p.cargo_process("cargo-build"), execs());
+ assert_that(&p.bin("foo"), existing_file());
+
+ assert_that(
+ process(p.bin("foo")),
+ execs().with_stdout("hello\n"));
+
+ assert_that(p.cargo_process("cargo-test"),
+ execs().with_stdout(format!("{} foo v0.5.0 (file:{})\n\n\
+ running 1 test\n\
+ test test_hello ... ok\n\n\
+ test result: ok. 1 passed; 0 failed; \
+ 0 ignored; 0 measured\n\n",
+ COMPILING, p.root().display())));
+
+ assert_that(&p.bin("tests/foo"), existing_file());
+})
mod test_cargo_compile;
mod test_cargo_compile_git_deps;
mod test_cargo_compile_path_deps;
+mod test_cargo_test;
mod test_shell;